package ma.controller.panels.auxillary;

import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;

import ma.view.gui.auxillary.NumericalTextField;
import ma.controller.panels.parameterpanels.CtrInPanel;
import ma.model.ManufacturingMethodInterface;
import ma.model.auxillary.Parameter;

/**
 * The {@code NumericalTFListener} class controls the user written
 * content of a parameter's text field where only numerical values should be
 * entered. If the input is valid the associated parameter gets set with it's
 * value. It also controls the behavior of the text field. 
 * @author Oliver Eckstein
 *
 */
public class NumericalTFListener implements DocumentListener {

    private NumericalTextField valueTF;
    private ManufacturingMethodInterface manuMethod;
    private Parameter param;    
    private CtrInPanel inputController;    
    private boolean oldErrorStatus = false;
    private String oldText;

    /**
     * To create a new instance of a {@code NumericalTextFieldListener}. 
     * @param valueTF the text field to be controlled.
     * @param manuMethod the manufacturing method whose parameter gets set.
     * @param param the parameter that is associated with the text field.
     * @param inputController the controller of all text fields of a panel.
     */
    public NumericalTFListener(
            NumericalTextField valueTF,
            ManufacturingMethodInterface manuMethod,
            Parameter param,
            CtrInPanel inputController)
    {
        this.valueTF = valueTF;
        this.manuMethod = manuMethod;
        this.param = param;
        this.inputController = inputController;
        oldText = valueTF.getText();
    }

    @Override
    public void insertUpdate(DocumentEvent e) {
        updateParamValue(e);        
    }

    @Override
    public void removeUpdate(DocumentEvent e) {
        updateParamValue(e);        
    }

    @Override
    public void changedUpdate(DocumentEvent e) {

    }

    /**
     * To handle the update of a parameter.
     * @param e the document event.
     */
    private void updateParamValue(DocumentEvent e) {        
        // Get the new input.
        String txt = "";
        try {
            txt = e.getDocument().getText(0, e.getDocument().getLength());
        } catch (BadLocationException e1) {            
            e1.printStackTrace();
        }
        
        Double newValue;        
        try {            
            /*
             * The input 5d or 5f is actually valid:
             * 5d = (Double) 5.0; 5f = (Float) 5.0.
             * To prevent this another letter gets appended to make
             * Double.valueOf(String) throw a NumberFormatException.
             */
            
            if (txt.contains("d") || txt.contains("f")) {
                txt += "a";
            }
            
            // To prevent causing an error when negative numbers get written.
            if (txt.equals("-")) {
                setTextFieldAndInfoPanel(null, txt);                
                return;
            }
            
            newValue = Double.valueOf(txt);            
                        
            manuMethod.setParameter(param, newValue);
            setTextFieldAndInfoPanel(null, txt);
            
        } catch (NumberFormatException nfe) {            
            newValue = Double.NaN;
            manuMethod.setParameter(param, newValue);            
            setTextFieldAndInfoPanel(nfe, txt);            
        }
        
        // Actually, we don't need the model to set a parameter's value.
        // We could also use param.setValue(param);
        // But since there can be a direct connection between several
        // parameters (i.e. f and v_f), the model has to check if one of the
        // other parameter is set and if so it has to set the counter
        // parameter. Therefore the model is necessary here.
        manuMethod.notifyObservers();
    }
    
    /**
     * Handles the state of the text field based on the given input. Also
     * updates the information panel.
     * @param e a possible thrown exception.
     * @param txt the entered text.
     */
    private void setTextFieldAndInfoPanel(Exception e, String txt) {
        if(e == null) {
            //No error occurred, old error indication can be deleted
            if(valueTF.getErrorStatus())
            {
                valueTF.clearError();                
            }            
            valueTF.setIsNumerical(true);
                        
            if(oldText.equals("") || oldErrorStatus) {
                inputController.addValidInput();
            }            
            oldText = txt;
            
        } else {
            // Handle error indication on text field
            if(txt.equals("")) {                
                valueTF.clearError();
            } else {
                valueTF.showError();                
            }

            valueTF.setIsNumerical(false);            
            
            // Handle active/disabled button
            if(!oldErrorStatus && !oldText.equals("")) {
                inputController.removeValidInput();
            }
            oldText = txt;
        }
        
        // Update the input controller's count of invalid inputs.
        boolean newErrorStatus = valueTF.getErrorStatus();
        if(!oldErrorStatus && newErrorStatus) {
            inputController.addInvalidInput();            
        } else if(oldErrorStatus && !newErrorStatus) {
            inputController.removeInvalidInput();
        }
        oldErrorStatus = newErrorStatus;
    }
}
